1 R Setup and Required Packages

In the following code chunk we load the packages used to support our analysis.

# check if packages are not installed; if yes, install missing packages
packages = c("tidyverse", "magrittr", # typical data analysis packages
             "MALDIquant", # match closest points between two vectors
             "foreach", "doParallel", "parallel", # packages for parallelization
             "R.matlab")
newPackages = packages[!(packages %in% installed.packages()[,"Package"])]
if(length(newPackages) > 0) install.packages(newPackages)

# using the library command to load all packages; invisible used to avoid printing all packages and dependencies used
invisible(lapply(packages, library, character.only = TRUE))

# source("./Functions.R") # our custom built functions

# set.seed(2020)
# startTime <- Sys.time()

2 Gait Acceleration Data and Rational Subgroups

We converted the raw IMU acceleration signals in the local to the global reference frame and removed the gravity. The acceleration signals were then transformed back to the local reference frame. Sagittal acceleration, lateral acceleration, and acceleration magnitude signals were then calculated. We used the vertical acceleration component to segment the gait cycles in order to isolate individual gait cycles. The acceleration profiles along with the experimental time stamps were stored in mat files.

In this code chunk we load the segmented acceleration mat files. The \(1^{st}\) 10 minutes of the data were considered as warm up period and thus excluded form the analysis. The acceleration profiles of the gait cycles during each walking cycle were grouped into a rational subgroup. We stored the start and end times of the walking cycles in csv files and loaded and used them in the following code chunk. The acceleration profiles within each start and end times to subgroup the gait cycles.

for (id in setdiff(1:15, 13)) {
  #################### Read start and end time of the subgroups
  video <- read.csv(paste0(file="../Data/csvFiles/Sub", id, ".csv"))

  video_leave <- video$Leaves
  video_leave <- video_leave[!is.na(video_leave)]
  video_leave <- video_leave - 600
  video_leave <- video_leave[video_leave > 0]

  video_enter <- video$Enters
  video_enter <- video_enter[!is.na(video_enter)]
  video_enter <- video_enter - 600
  video_enter <- video_enter[video_enter > 0]

  ################################################### Read from mat files
  raw1 <- readMat(paste0("../Data/matFiles/Subject", id, "_aZ_seg.mat"))

  raw2 <- raw1$gait

  num_rows <- length(raw2)/5

  aM <- list("vector")
  aS <- list("vector")
  aL <- list("vector")
  exp_time <- c()
  for (i in 1:num_rows) {
    aM[[i]] <- raw2[[1*num_rows + i]][[1]][1,]
    aS[[i]] <- raw2[[2*num_rows + i]][[1]][1,]
    aL[[i]] <- raw2[[3*num_rows + i]][[1]][1,]
    exp_time[i] <- raw2[[i+4*num_rows]][[1]][1,1]
  }

  ###################################################### Create subgroups
  if (video_leave[1] > video_enter[1]){
    video_leave <- c(exp_time[1], video_leave)
  }

  if (tail(video_leave, 1) > tail(video_enter, 1)){
    video_enter <- c(video_enter, tail(exp_time, 1))
  }

  conf <- 0.05 * mean(video_enter - video_leave) # 5% confidence for each subgroup

  # Match video subroup times against the experimental times from mat files
  leave_index <- match.closest(video_leave + conf, exp_time)
  enter_index <- match.closest(video_enter - conf, exp_time)

  aM_list <- list()
  aS_list <- list()
  aL_list <- list()
  t_list <- list()
  for (i in 1:length(leave_index)) {
    aM_list[[i]] <- aM[leave_index[i]:enter_index[i]]
    aS_list[[i]] <- aS[leave_index[i]:enter_index[i]]
    aL_list[[i]] <- aL[leave_index[i]:enter_index[i]]
    t_list[[i]] <- exp_time[leave_index[i]:enter_index[i]]
  }

  len <- c()
  for (i in 1:length(aM_list)) {
    len <- c(len, lengths(aM_list[[i]]))
  }

  cut_len <- quantile(len, 0.02)

  for (i in 1:length(aM_list)) {
    remove_ind <- which(lengths(aM_list[[i]]) < cut_len)
    remove_ind <- c(remove_ind, 3000)
    aM_list[[i]] <- aM_list[[i]][-remove_ind]
    aS_list[[i]] <- aS_list[[i]][-remove_ind]
    aL_list[[i]] <- aL_list[[i]][-remove_ind]
    t_list[[i]] <- t_list[[i]][-remove_ind]
  }

  batchSize <- lengths(aM_list)

  ###############################################
  aM_list_all <- do.call(c, aM_list)
  aS_list_all <- do.call(c, aS_list)
  aL_list_all <- do.call(c, aL_list)
  t_all <- do.call(c, t_list)
  ###############################################
  aM_mat <- do.call(rbind, aM_list_all)[,1:cut_len]
  aS_mat <- do.call(rbind, aS_list_all)[,1:cut_len]
  aL_mat <- do.call(rbind, aL_list_all)[,1:cut_len]

  ##################
  assign(paste0("sub", id, "_aMSLT"),
         list(aMag=aM_mat, aSag=aS_mat, aLat=aL_mat, time=t_all, batchSize=batchSize))
}

########################################### Save
save(sub1_aMSLT, sub2_aMSLT, sub3_aMSLT, sub4_aMSLT, sub5_aMSLT, sub6_aMSLT,
     sub7_aMSLT, sub8_aMSLT, sub9_aMSLT, sub10_aMSLT, sub11_aMSLT, sub12_aMSLT,
     sub14_aMSLT, sub15_aMSLT,

     file="../Data/rData/subGs_for_depth.Rdata")

3 Depth Calculation

Explain about depth concept in general; centrality and outlyingness

3.1 Mode Depth

Calculate mode depth…

load(file="../Data/rData/subGs_for_depth.Rdata")

cores = detectCores() - 2 # to give the server some breathing Room
cl = makePSOCKcluster(cores)
registerDoParallel(cl)

for (id in setdiff(1:15, 13)) {
  
  aMag <- get(paste0("sub", id, "_aMSLT"))$aMag
  tExp <- get(paste0("sub", id, "_aMSLT"))$time
  
  ################################## Mode Depth
  inControl <- aMag[1:500,]
  inControlDepth <- {depth.mode(fdata(inControl))}$dep
  
  ###################### foreach
  onlineDepth <- c()
  end <- nrow(aMag)
  onlineDepth <- foreach(i=501:end, .packages = c('fda.usc', 'tidyverse'),
                         .combine='c') %dopar% {
                           temp1 <- aMag %>% .[i,]
                           append1 <- rbind(inControl, temp1)
                           temp2 <- depth.mode(fdata(append1))
                           
                           temp3 <- temp2$dep %>% .[501]
                           temp3
                         }
  
  modeDepth <- c(inControlDepth, onlineDepth)
  
  assign(paste0("sub", id, "_mode_mag"),
         list(modeDepth=modeDepth, tExp=tExp))
}

save(sub1_mode_mag, sub2_mode_mag, sub3_mode_mag, sub4_mode_mag, sub5_mode_mag, sub6_mode_mag,
     sub7_mode_mag, sub8_mode_mag, sub9_mode_mag, sub10_mode_mag, sub11_mode_mag, sub12_mode_mag,
     sub14_mode_mag, sub15_mode_mag,
     file="../Data/rData/mode_mag.Rdata")

3.2 Visualize the Mode Depth

load(file="../Data/rData/mode_mag.Rdata")

for (id in setdiff(1:15, 13)) {
  depthMode <- get(paste0("sub", id, "_mode_mag"))$modeDepth
  tMode <- get(paste0("sub", id, "_mode_mag"))$tExp

  cat("###", paste0("Subject", id, " Mode Depth"), "{-}",'\n')
  plot(tMode, depthMode, pch=16, cex=1,col=c(rep("red", 500), rep("black", length(depthMode)-500)))
  legend("topright", legend=c("Baseline data", "New data"), col=c("red", "black"))
  cat('\n \n')

}

Subject1 Mode Depth

Subject2 Mode Depth

Subject3 Mode Depth

Subject4 Mode Depth

Subject5 Mode Depth

Subject6 Mode Depth

Subject7 Mode Depth

Subject8 Mode Depth

Subject9 Mode Depth

Subject10 Mode Depth

Subject11 Mode Depth

Subject12 Mode Depth

Subject14 Mode Depth

Subject15 Mode Depth

3.3 MFHD Depth

Calculate MFHD depth…

Explain since it take very long it was performed on supercomputer cluster in parallel and the eval option in the following code chunk was set to false since this could take weeks to run on the local computer.

#################################################
load(file="../Data/rData/subGs_for_depth.Rdata")

cores = detectCores() - 2 # to give the server some breathing Room
cl = makePSOCKcluster(cores)
registerDoParallel(cl)

for (id in setdiff(1:15, 13)) {
  
  aSag <- get(paste0("sub", id, "_aMSLT"))$aSag
  aLat <- get(paste0("sub", id, "_aMSLT"))$aLat
  tExp <- get(paste0("sub", id, "_aMSLT"))$time
  
  ################################## Mode Depth
  inControlSag <- aSag[1:500,]
  inControlLat <- aLat[1:500,]
  inControlDepth <- {MFHD(y1=inControlSag, y2=inControlLat, alpha=0.125, Beta=0.5)}$MFHDdepth[1,]
  
  ###################### foreach
  onlineDepth <- c()
  end <- nrow(aSag)
  onlineDepth <- foreach(i=501:end, .packages = c('fda.usc', 'tidyverse', 'MFHD'),
                         .combine='c') %dopar% {
                           temp1 <- aSag %>% .[i,]
                           temp2 <- aLat %>% .[i,]
                           
                           append1 <- rbind(inControlSag, temp1)
                           append2 <- rbind(inControlLat, temp2)
                           
                           temp3 <- MFHD(y1=append1, y2=append2, alpha=0.125, Beta=0.5)
                           
                           temp4 <- temp3$MFHDdepth %>% .[1,501]
                           temp4
                         }
  
  MFHDdepth <- c(inControlDepth, onlineDepth)
  
  assign(paste0("sub", id, "_MFHD"),
         list(MFHDdepth=MFHDdepth, tExp=tExp))
}

save(sub1_MFHD, sub2_MFHD, sub3_MFHD, sub4_MFHD, sub5_MFHD, sub6_MFHD, sub7_MFHD, sub8_MFHD,
     sub9_MFHD, sub10_MFHD, sub11_MFHD, sub12_MFHD, sub14_MFHD, sub15_MFHD,
     file="../Data/rData/MFHD.Rdata")

3.4 Visualize the MFHD Depth

load(file="../Data/rData/MFHD.Rdata")

for (id in setdiff(1:15, 13)) {
  depthMFHD <- get(paste0("sub", id, "_MFHD"))$MFHDdepth
  tMFHD <- get(paste0("sub", id, "_MFHD"))$tExp
  
  cat("###", paste0("Subject", id, " MFHD Depth"), "{-}",'\n')
  plot(tMFHD, depthMFHD, pch=16, cex=1,col=c(rep("red", 500), rep("black", length(depthMFHD)-500)))
  legend("topright", legend=c("Baseline data", "New data"), col=c("red", "black"),
         pch=16)
  cat('\n \n')
  
}

Subject1 MFHD Depth

Subject2 MFHD Depth

Subject3 MFHD Depth

Subject4 MFHD Depth

Subject5 MFHD Depth

Subject6 MFHD Depth

Subject7 MFHD Depth

Subject8 MFHD Depth

Subject9 MFHD Depth

Subject10 MFHD Depth

Subject11 MFHD Depth

Subject12 MFHD Depth

Subject14 MFHD Depth

Subject15 MFHD Depth


References


  1. Email: | Website: LinkedIn↩︎

  2. Email: | Phone: +1-716-645-6063 | Website: University at Buffalo Official↩︎

  3. Email: | Phone: +1-716-645-4696 | Website: University at Buffalo Official↩︎

  4. Email: | Phone: +1-513-529-4185 | Website: Miami University Official↩︎

  5. Email: | Phone: +1-513-529-4823 | Website: Miami University Official↩︎

LS0tDQp0aXRsZTogIkEgcGVyc29uYWxpemVkIGFuZCBub24tcGFyYW1ldHJpYyBmcmFtZXdvcmsgZm9yIGRldGVjdGluZyBjaGFuZ2VzIGluIGdhaXQgY3ljbGVzIg0KYXV0aG9yOg0KICAtIG5hbWU6ICJTYWViIFJhZ2FuaSBMYW1vb2tpIF5bRW1haWw6IHNhZWJyYWdhQGJ1ZmZhbG8uZWR1IHwgV2Vic2l0ZTogPGEgaHJlZj1cImh0dHBzOi8vd3d3LmxpbmtlZGluLmNvbS9pbi9zYWViLXJhZ2FuaS1sYW1vb2tpLTEyM2E5NjU4L1wiPkxpbmtlZEluPC9hPl0iDQogICAgYWZmaWxpYXRpb246IERlcGFydG1lbnQgb2YgTWVjaGFuaWNhbCAmIEFlcm9zcGFjZSBFbmdpbmVlcmluZywgVW5pdmVyc2l0eSBhdCBCdWZmYWxvDQogIC0gbmFtZTogIkppeWVvbiBLYW5nIF5bRW1haWw6IGppeWVvbmtAYnVmZmFsby5lZHUgfCBQaG9uZTogKzEtNzE2LTY0NS02MDYzIHwgV2Vic2l0ZTogPGEgaHJlZj1cImh0dHA6Ly9lbmdpbmVlcmluZy5idWZmYWxvLmVkdS9pbmR1c3RyaWFsLXN5c3RlbXMvcGVvcGxlL2ZhY3VsdHktZGlyZWN0b3J5L2otamFuZy5odG1sXCI+VW5pdmVyc2l0eSBhdCBCdWZmYWxvIE9mZmljaWFsPC9hPl0iDQogICAgYWZmaWxpYXRpb246IERlcGFydG1lbnQgb2YgTWVjaGFuaWNhbCAmIEFlcm9zcGFjZSBFbmdpbmVlcmluZywgVW5pdmVyc2l0eSBhdCBCdWZmYWxvDQogIC0gbmFtZTogIkxvcmEgQS4gQ2F2dW90byBeW0VtYWlsOiBsb3JhY2F2dUBidWZmYWxvLmVkdSB8IFBob25lOiArMS03MTYtNjQ1LTQ2OTYgfCBXZWJzaXRlOiA8YSBocmVmPVwiaHR0cDovL2VuZ2luZWVyaW5nLmJ1ZmZhbG8uZWR1L2luZHVzdHJpYWwtc3lzdGVtcy9wZW9wbGUvZmFjdWx0eS1kaXJlY3RvcnkvY2F2dW90by1sb3JhLmh0bWxcIj5Vbml2ZXJzaXR5IGF0IEJ1ZmZhbG8gT2ZmaWNpYWw8L2E+XSINCiAgICBhZmZpbGlhdGlvbjogRGVwYXJ0bWVudCBvZiBJbmR1c3RyaWFsIGFuZCBTeXN0ZW1zIEVuZ2luZWVyaW5nLCBVbml2ZXJzaXR5IGF0IEJ1ZmZhbG8NCiAgLSBuYW1lOiAiRmFkZWwgTS4gTWVnYWhlZCBeW0VtYWlsOiBmbWVnYWhlZEBtaWFtaW9oLmVkdSB8IFBob25lOiArMS01MTMtNTI5LTQxODUgfCBXZWJzaXRlOiA8YSBocmVmPVwiaHR0cHM6Ly9taWFtaW9oLmVkdS9mc2IvZGlyZWN0b3J5Lz91cD0vZGlyZWN0b3J5L21lZ2FoZWZtXCI+TWlhbWkgVW5pdmVyc2l0eSBPZmZpY2lhbDwvYT5dIg0KICAgIGFmZmlsaWF0aW9uOiBGYXJtZXIgU2Nob29sIG9mIEJ1c2luZXNzLCBNaWFtaSBVbml2ZXJzaXR5DQogIC0gbmFtZTogIkFsbGlzb24gSm9uZXMgRmFybWVyIF5bRW1haWw6IGZhcm1lcmwyQG1pYW1pb2guZWR1IHwgUGhvbmU6ICsxLTUxMy01MjktNDgyMyB8IFdlYnNpdGU6IDxhIGhyZWY9XCJodHRwczovL21pYW1pb2guZWR1L2ZzYi9kaXJlY3RvcnkvP3VwPS9kaXJlY3RvcnkvZmFybWVybDJcIj5NaWFtaSBVbml2ZXJzaXR5IE9mZmljaWFsPC9hPl0iDQogICAgYWZmaWxpYXRpb246IEZhcm1lciBTY2hvb2wgb2YgQnVzaW5lc3MsIE1pYW1pIFVuaXZlcnNpdHkNCmJpYmxpb2dyYXBoeTogRVdNQVJlZnMuYmliDQpjc2w6IGFwYS5jc2wNCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgbnVtYmVyX3NlY3Rpb25zOiBUUlVFDQogICAgdGhlbWU6IHNpbXBsZXgNCiAgICBwYWdlZF9kZjogVFJVRQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCiAgaW5jbHVkZXM6DQogICAgaW5faGVhZGVyOiBzdHJ1Y3R1cmUudGV4DQotLS0NCg0KDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgY2FjaGUgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgIHByb2dyZXNzID0gRkFMU0UsIA0KICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBkcGkgPSA2MDApDQpvcHRpb25zKHF3cmFwczJfbWFya3VwID0gIm1hcmtkb3duIikNCg0KDQpgYGANCg0KLS0tDQoNCiMgUiBTZXR1cCBhbmQgUmVxdWlyZWQgUGFja2FnZXMNCg0KSW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rIHdlIGxvYWQgdGhlIHBhY2thZ2VzIHVzZWQgdG8gc3VwcG9ydCBvdXIgYW5hbHlzaXMuICANCg0KYGBge3IgcGFja2FnZXMsIGNhY2hlPUZBTFNFfQ0KDQojIGNoZWNrIGlmIHBhY2thZ2VzIGFyZSBub3QgaW5zdGFsbGVkOyBpZiB5ZXMsIGluc3RhbGwgbWlzc2luZyBwYWNrYWdlcw0KcGFja2FnZXMgPSBjKCJ0aWR5dmVyc2UiLCAibWFncml0dHIiLCAjIHR5cGljYWwgZGF0YSBhbmFseXNpcyBwYWNrYWdlcw0KICAgICAgICAgICAgICJNQUxESXF1YW50IiwgIyBtYXRjaCBjbG9zZXN0IHBvaW50cyBiZXR3ZWVuIHR3byB2ZWN0b3JzDQogICAgICAgICAgICAgImZvcmVhY2giLCAiZG9QYXJhbGxlbCIsICJwYXJhbGxlbCIsICMgcGFja2FnZXMgZm9yIHBhcmFsbGVsaXphdGlvbg0KICAgICAgICAgICAgICJSLm1hdGxhYiIpDQpuZXdQYWNrYWdlcyA9IHBhY2thZ2VzWyEocGFja2FnZXMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKVssIlBhY2thZ2UiXSldDQppZihsZW5ndGgobmV3UGFja2FnZXMpID4gMCkgaW5zdGFsbC5wYWNrYWdlcyhuZXdQYWNrYWdlcykNCg0KIyB1c2luZyB0aGUgbGlicmFyeSBjb21tYW5kIHRvIGxvYWQgYWxsIHBhY2thZ2VzOyBpbnZpc2libGUgdXNlZCB0byBhdm9pZCBwcmludGluZyBhbGwgcGFja2FnZXMgYW5kIGRlcGVuZGVuY2llcyB1c2VkDQppbnZpc2libGUobGFwcGx5KHBhY2thZ2VzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKQ0KDQojIHNvdXJjZSgiLi9GdW5jdGlvbnMuUiIpICMgb3VyIGN1c3RvbSBidWlsdCBmdW5jdGlvbnMNCg0KIyBzZXQuc2VlZCgyMDIwKQ0KIyBzdGFydFRpbWUgPC0gU3lzLnRpbWUoKQ0KYGBgDQoNCg0KLS0tDQoNCiMgR2FpdCBBY2NlbGVyYXRpb24gRGF0YSBhbmQgUmF0aW9uYWwgU3ViZ3JvdXBzDQoNCldlIGNvbnZlcnRlZCB0aGUgcmF3IElNVSBhY2NlbGVyYXRpb24gc2lnbmFscyBpbiB0aGUgbG9jYWwgdG8gdGhlIGdsb2JhbCByZWZlcmVuY2UgZnJhbWUgYW5kIHJlbW92ZWQgdGhlIGdyYXZpdHkuIFRoZSBhY2NlbGVyYXRpb24gc2lnbmFscyB3ZXJlIHRoZW4gdHJhbnNmb3JtZWQgYmFjayB0byB0aGUgbG9jYWwgcmVmZXJlbmNlIGZyYW1lLiBTYWdpdHRhbCBhY2NlbGVyYXRpb24sIGxhdGVyYWwgYWNjZWxlcmF0aW9uLCBhbmQgYWNjZWxlcmF0aW9uIG1hZ25pdHVkZSBzaWduYWxzIHdlcmUgdGhlbiBjYWxjdWxhdGVkLiBXZSB1c2VkIHRoZSB2ZXJ0aWNhbCBhY2NlbGVyYXRpb24gY29tcG9uZW50IHRvIHNlZ21lbnQgdGhlIGdhaXQgY3ljbGVzIGluIG9yZGVyIHRvIGlzb2xhdGUgaW5kaXZpZHVhbCBnYWl0IGN5Y2xlcy4gVGhlIGFjY2VsZXJhdGlvbiBwcm9maWxlcyBhbG9uZyB3aXRoIHRoZSBleHBlcmltZW50YWwgdGltZSBzdGFtcHMgd2VyZSBzdG9yZWQgaW4gbWF0IGZpbGVzLg0KDQpJbiB0aGlzIGNvZGUgY2h1bmsgd2UgbG9hZCB0aGUgc2VnbWVudGVkIGFjY2VsZXJhdGlvbiBtYXQgZmlsZXMuIFRoZSAkMV57c3R9JCAxMCBtaW51dGVzIG9mIHRoZSBkYXRhIHdlcmUgY29uc2lkZXJlZCBhcyB3YXJtIHVwIHBlcmlvZCBhbmQgdGh1cyBleGNsdWRlZCBmb3JtIHRoZSBhbmFseXNpcy4gVGhlIGFjY2VsZXJhdGlvbiBwcm9maWxlcyBvZiB0aGUgZ2FpdCBjeWNsZXMgZHVyaW5nIGVhY2ggd2Fsa2luZyBjeWNsZSB3ZXJlIGdyb3VwZWQgaW50byBhIHJhdGlvbmFsIHN1Ymdyb3VwLiBXZSBzdG9yZWQgdGhlIHN0YXJ0IGFuZCBlbmQgdGltZXMgb2YgdGhlIHdhbGtpbmcgY3ljbGVzIGluIGNzdiBmaWxlcyBhbmQgbG9hZGVkIGFuZCB1c2VkIHRoZW0gaW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLiBUaGUgYWNjZWxlcmF0aW9uIHByb2ZpbGVzIHdpdGhpbiBlYWNoIHN0YXJ0IGFuZCBlbmQgdGltZXMgdG8gc3ViZ3JvdXAgdGhlIGdhaXQgY3ljbGVzLg0KDQoNCmBgYHtyIHJlYWQtc3ViZ30NCg0KZm9yIChpZCBpbiBzZXRkaWZmKDE6MTUsIDEzKSkgew0KICAjIyMjIyMjIyMjIyMjIyMjIyMjIyBSZWFkIHN0YXJ0IGFuZCBlbmQgdGltZSBvZiB0aGUgc3ViZ3JvdXBzDQogIHZpZGVvIDwtIHJlYWQuY3N2KHBhc3RlMChmaWxlPSIuLi9EYXRhL2NzdkZpbGVzL1N1YiIsIGlkLCAiLmNzdiIpKQ0KDQogIHZpZGVvX2xlYXZlIDwtIHZpZGVvJExlYXZlcw0KICB2aWRlb19sZWF2ZSA8LSB2aWRlb19sZWF2ZVshaXMubmEodmlkZW9fbGVhdmUpXQ0KICB2aWRlb19sZWF2ZSA8LSB2aWRlb19sZWF2ZSAtIDYwMA0KICB2aWRlb19sZWF2ZSA8LSB2aWRlb19sZWF2ZVt2aWRlb19sZWF2ZSA+IDBdDQoNCiAgdmlkZW9fZW50ZXIgPC0gdmlkZW8kRW50ZXJzDQogIHZpZGVvX2VudGVyIDwtIHZpZGVvX2VudGVyWyFpcy5uYSh2aWRlb19lbnRlcildDQogIHZpZGVvX2VudGVyIDwtIHZpZGVvX2VudGVyIC0gNjAwDQogIHZpZGVvX2VudGVyIDwtIHZpZGVvX2VudGVyW3ZpZGVvX2VudGVyID4gMF0NCg0KICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMgUmVhZCBmcm9tIG1hdCBmaWxlcw0KICByYXcxIDwtIHJlYWRNYXQocGFzdGUwKCIuLi9EYXRhL21hdEZpbGVzL1N1YmplY3QiLCBpZCwgIl9hWl9zZWcubWF0IikpDQoNCiAgcmF3MiA8LSByYXcxJGdhaXQNCg0KICBudW1fcm93cyA8LSBsZW5ndGgocmF3MikvNQ0KDQogIGFNIDwtIGxpc3QoInZlY3RvciIpDQogIGFTIDwtIGxpc3QoInZlY3RvciIpDQogIGFMIDwtIGxpc3QoInZlY3RvciIpDQogIGV4cF90aW1lIDwtIGMoKQ0KICBmb3IgKGkgaW4gMTpudW1fcm93cykgew0KICAgIGFNW1tpXV0gPC0gcmF3MltbMSpudW1fcm93cyArIGldXVtbMV1dWzEsXQ0KICAgIGFTW1tpXV0gPC0gcmF3MltbMipudW1fcm93cyArIGldXVtbMV1dWzEsXQ0KICAgIGFMW1tpXV0gPC0gcmF3MltbMypudW1fcm93cyArIGldXVtbMV1dWzEsXQ0KICAgIGV4cF90aW1lW2ldIDwtIHJhdzJbW2krNCpudW1fcm93c11dW1sxXV1bMSwxXQ0KICB9DQoNCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIENyZWF0ZSBzdWJncm91cHMNCiAgaWYgKHZpZGVvX2xlYXZlWzFdID4gdmlkZW9fZW50ZXJbMV0pew0KICAgIHZpZGVvX2xlYXZlIDwtIGMoZXhwX3RpbWVbMV0sIHZpZGVvX2xlYXZlKQ0KICB9DQoNCiAgaWYgKHRhaWwodmlkZW9fbGVhdmUsIDEpID4gdGFpbCh2aWRlb19lbnRlciwgMSkpew0KICAgIHZpZGVvX2VudGVyIDwtIGModmlkZW9fZW50ZXIsIHRhaWwoZXhwX3RpbWUsIDEpKQ0KICB9DQoNCiAgY29uZiA8LSAwLjA1ICogbWVhbih2aWRlb19lbnRlciAtIHZpZGVvX2xlYXZlKSAjIDUlIGNvbmZpZGVuY2UgZm9yIGVhY2ggc3ViZ3JvdXANCg0KICAjIE1hdGNoIHZpZGVvIHN1YnJvdXAgdGltZXMgYWdhaW5zdCB0aGUgZXhwZXJpbWVudGFsIHRpbWVzIGZyb20gbWF0IGZpbGVzDQogIGxlYXZlX2luZGV4IDwtIG1hdGNoLmNsb3Nlc3QodmlkZW9fbGVhdmUgKyBjb25mLCBleHBfdGltZSkNCiAgZW50ZXJfaW5kZXggPC0gbWF0Y2guY2xvc2VzdCh2aWRlb19lbnRlciAtIGNvbmYsIGV4cF90aW1lKQ0KDQogIGFNX2xpc3QgPC0gbGlzdCgpDQogIGFTX2xpc3QgPC0gbGlzdCgpDQogIGFMX2xpc3QgPC0gbGlzdCgpDQogIHRfbGlzdCA8LSBsaXN0KCkNCiAgZm9yIChpIGluIDE6bGVuZ3RoKGxlYXZlX2luZGV4KSkgew0KICAgIGFNX2xpc3RbW2ldXSA8LSBhTVtsZWF2ZV9pbmRleFtpXTplbnRlcl9pbmRleFtpXV0NCiAgICBhU19saXN0W1tpXV0gPC0gYVNbbGVhdmVfaW5kZXhbaV06ZW50ZXJfaW5kZXhbaV1dDQogICAgYUxfbGlzdFtbaV1dIDwtIGFMW2xlYXZlX2luZGV4W2ldOmVudGVyX2luZGV4W2ldXQ0KICAgIHRfbGlzdFtbaV1dIDwtIGV4cF90aW1lW2xlYXZlX2luZGV4W2ldOmVudGVyX2luZGV4W2ldXQ0KICB9DQoNCiAgbGVuIDwtIGMoKQ0KICBmb3IgKGkgaW4gMTpsZW5ndGgoYU1fbGlzdCkpIHsNCiAgICBsZW4gPC0gYyhsZW4sIGxlbmd0aHMoYU1fbGlzdFtbaV1dKSkNCiAgfQ0KDQogIGN1dF9sZW4gPC0gcXVhbnRpbGUobGVuLCAwLjAyKQ0KDQogIGZvciAoaSBpbiAxOmxlbmd0aChhTV9saXN0KSkgew0KICAgIHJlbW92ZV9pbmQgPC0gd2hpY2gobGVuZ3RocyhhTV9saXN0W1tpXV0pIDwgY3V0X2xlbikNCiAgICByZW1vdmVfaW5kIDwtIGMocmVtb3ZlX2luZCwgMzAwMCkNCiAgICBhTV9saXN0W1tpXV0gPC0gYU1fbGlzdFtbaV1dWy1yZW1vdmVfaW5kXQ0KICAgIGFTX2xpc3RbW2ldXSA8LSBhU19saXN0W1tpXV1bLXJlbW92ZV9pbmRdDQogICAgYUxfbGlzdFtbaV1dIDwtIGFMX2xpc3RbW2ldXVstcmVtb3ZlX2luZF0NCiAgICB0X2xpc3RbW2ldXSA8LSB0X2xpc3RbW2ldXVstcmVtb3ZlX2luZF0NCiAgfQ0KDQogIGJhdGNoU2l6ZSA8LSBsZW5ndGhzKGFNX2xpc3QpDQoNCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiAgYU1fbGlzdF9hbGwgPC0gZG8uY2FsbChjLCBhTV9saXN0KQ0KICBhU19saXN0X2FsbCA8LSBkby5jYWxsKGMsIGFTX2xpc3QpDQogIGFMX2xpc3RfYWxsIDwtIGRvLmNhbGwoYywgYUxfbGlzdCkNCiAgdF9hbGwgPC0gZG8uY2FsbChjLCB0X2xpc3QpDQogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQogIGFNX21hdCA8LSBkby5jYWxsKHJiaW5kLCBhTV9saXN0X2FsbClbLDE6Y3V0X2xlbl0NCiAgYVNfbWF0IDwtIGRvLmNhbGwocmJpbmQsIGFTX2xpc3RfYWxsKVssMTpjdXRfbGVuXQ0KICBhTF9tYXQgPC0gZG8uY2FsbChyYmluZCwgYUxfbGlzdF9hbGwpWywxOmN1dF9sZW5dDQoNCiAgIyMjIyMjIyMjIyMjIyMjIyMjDQogIGFzc2lnbihwYXN0ZTAoInN1YiIsIGlkLCAiX2FNU0xUIiksDQogICAgICAgICBsaXN0KGFNYWc9YU1fbWF0LCBhU2FnPWFTX21hdCwgYUxhdD1hTF9tYXQsIHRpbWU9dF9hbGwsIGJhdGNoU2l6ZT1iYXRjaFNpemUpKQ0KfQ0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIFNhdmUNCnNhdmUoc3ViMV9hTVNMVCwgc3ViMl9hTVNMVCwgc3ViM19hTVNMVCwgc3ViNF9hTVNMVCwgc3ViNV9hTVNMVCwgc3ViNl9hTVNMVCwNCiAgICAgc3ViN19hTVNMVCwgc3ViOF9hTVNMVCwgc3ViOV9hTVNMVCwgc3ViMTBfYU1TTFQsIHN1YjExX2FNU0xULCBzdWIxMl9hTVNMVCwNCiAgICAgc3ViMTRfYU1TTFQsIHN1YjE1X2FNU0xULA0KDQogICAgIGZpbGU9Ii4uL0RhdGEvckRhdGEvc3ViR3NfZm9yX2RlcHRoLlJkYXRhIikNCmBgYA0KDQoNCiMgRGVwdGggQ2FsY3VsYXRpb24NCg0KRXhwbGFpbiBhYm91dCBkZXB0aCBjb25jZXB0IGluIGdlbmVyYWw7IGNlbnRyYWxpdHkgYW5kIG91dGx5aW5nbmVzcw0KDQojIyBNb2RlIERlcHRoDQoNCkNhbGN1bGF0ZSBtb2RlIGRlcHRoLi4uDQoNCmBgYHtyIG1vZGUtZGVwdGgsIGV2YWw9RkFMU0V9DQoNCmxvYWQoZmlsZT0iLi4vRGF0YS9yRGF0YS9zdWJHc19mb3JfZGVwdGguUmRhdGEiKQ0KDQpjb3JlcyA9IGRldGVjdENvcmVzKCkgLSAyICMgdG8gZ2l2ZSB0aGUgc2VydmVyIHNvbWUgYnJlYXRoaW5nIFJvb20NCmNsID0gbWFrZVBTT0NLY2x1c3Rlcihjb3JlcykNCnJlZ2lzdGVyRG9QYXJhbGxlbChjbCkNCg0KZm9yIChpZCBpbiBzZXRkaWZmKDE6MTUsIDEzKSkgew0KICANCiAgYU1hZyA8LSBnZXQocGFzdGUwKCJzdWIiLCBpZCwgIl9hTVNMVCIpKSRhTWFnDQogIHRFeHAgPC0gZ2V0KHBhc3RlMCgic3ViIiwgaWQsICJfYU1TTFQiKSkkdGltZQ0KICANCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyBNb2RlIERlcHRoDQogIGluQ29udHJvbCA8LSBhTWFnWzE6NTAwLF0NCiAgaW5Db250cm9sRGVwdGggPC0ge2RlcHRoLm1vZGUoZmRhdGEoaW5Db250cm9sKSl9JGRlcA0KICANCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyBmb3JlYWNoDQogIG9ubGluZURlcHRoIDwtIGMoKQ0KICBlbmQgPC0gbnJvdyhhTWFnKQ0KICBvbmxpbmVEZXB0aCA8LSBmb3JlYWNoKGk9NTAxOmVuZCwgLnBhY2thZ2VzID0gYygnZmRhLnVzYycsICd0aWR5dmVyc2UnKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAuY29tYmluZT0nYycpICVkb3BhciUgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVtcDEgPC0gYU1hZyAlPiUgLltpLF0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFwcGVuZDEgPC0gcmJpbmQoaW5Db250cm9sLCB0ZW1wMSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlbXAyIDwtIGRlcHRoLm1vZGUoZmRhdGEoYXBwZW5kMSkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlbXAzIDwtIHRlbXAyJGRlcCAlPiUgLls1MDFdDQogICAgICAgICAgICAgICAgICAgICAgICAgICB0ZW1wMw0KICAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgDQogIG1vZGVEZXB0aCA8LSBjKGluQ29udHJvbERlcHRoLCBvbmxpbmVEZXB0aCkNCiAgDQogIGFzc2lnbihwYXN0ZTAoInN1YiIsIGlkLCAiX21vZGVfbWFnIiksDQogICAgICAgICBsaXN0KG1vZGVEZXB0aD1tb2RlRGVwdGgsIHRFeHA9dEV4cCkpDQp9DQoNCnNhdmUoc3ViMV9tb2RlX21hZywgc3ViMl9tb2RlX21hZywgc3ViM19tb2RlX21hZywgc3ViNF9tb2RlX21hZywgc3ViNV9tb2RlX21hZywgc3ViNl9tb2RlX21hZywNCiAgICAgc3ViN19tb2RlX21hZywgc3ViOF9tb2RlX21hZywgc3ViOV9tb2RlX21hZywgc3ViMTBfbW9kZV9tYWcsIHN1YjExX21vZGVfbWFnLCBzdWIxMl9tb2RlX21hZywNCiAgICAgc3ViMTRfbW9kZV9tYWcsIHN1YjE1X21vZGVfbWFnLA0KICAgICBmaWxlPSIuLi9EYXRhL3JEYXRhL21vZGVfbWFnLlJkYXRhIikNCmBgYA0KDQojIyBWaXN1YWxpemUgdGhlIE1vZGUgRGVwdGggey50YWJzZXQgLnRhYnNldC1mYWRlfQ0KDQpgYGB7ciB2aXNfTW9kZSwgZmlnLmFsaWduPSJjZW50ZXIiLCByZXN1bHRzPSJhc2lzIiwgb3V0LndpZHRoPSIxMDAlIn0NCg0KbG9hZChmaWxlPSIuLi9EYXRhL3JEYXRhL21vZGVfbWFnLlJkYXRhIikNCg0KZm9yIChpZCBpbiBzZXRkaWZmKDE6MTUsIDEzKSkgew0KICBkZXB0aE1vZGUgPC0gZ2V0KHBhc3RlMCgic3ViIiwgaWQsICJfbW9kZV9tYWciKSkkbW9kZURlcHRoDQogIHRNb2RlIDwtIGdldChwYXN0ZTAoInN1YiIsIGlkLCAiX21vZGVfbWFnIikpJHRFeHANCg0KICBjYXQoIiMjIyIsIHBhc3RlMCgiU3ViamVjdCIsIGlkLCAiIE1vZGUgRGVwdGgiKSwgInstfSIsJ1xuJykNCiAgcGxvdCh0TW9kZSwgZGVwdGhNb2RlLCBwY2g9MTYsIGNleD0xLGNvbD1jKHJlcCgicmVkIiwgNTAwKSwgcmVwKCJibGFjayIsIGxlbmd0aChkZXB0aE1vZGUpLTUwMCkpKQ0KICBsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kPWMoIkJhc2VsaW5lIGRhdGEiLCAiTmV3IGRhdGEiKSwgY29sPWMoInJlZCIsICJibGFjayIpKQ0KICBjYXQoJ1xuIFxuJykNCg0KfQ0KDQpgYGANCg0KIyMgTUZIRCBEZXB0aA0KDQpDYWxjdWxhdGUgTUZIRCBkZXB0aC4uLg0KDQpFeHBsYWluIHNpbmNlIGl0IHRha2UgdmVyeSBsb25nIGl0IHdhcyBwZXJmb3JtZWQgb24gc3VwZXJjb21wdXRlciBjbHVzdGVyIGluIHBhcmFsbGVsIGFuZCB0aGUgZXZhbCBvcHRpb24gaW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rIHdhcyBzZXQgdG8gZmFsc2Ugc2luY2UgdGhpcyBjb3VsZCB0YWtlIHdlZWtzIHRvIHJ1biBvbiB0aGUgbG9jYWwgY29tcHV0ZXIuDQoNCmBgYHtyIE1GSEQtZGVwdGgsIGV2YWw9RkFMU0V9DQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmxvYWQoZmlsZT0iLi4vRGF0YS9yRGF0YS9zdWJHc19mb3JfZGVwdGguUmRhdGEiKQ0KDQpjb3JlcyA9IGRldGVjdENvcmVzKCkgLSAyICMgdG8gZ2l2ZSB0aGUgc2VydmVyIHNvbWUgYnJlYXRoaW5nIFJvb20NCmNsID0gbWFrZVBTT0NLY2x1c3Rlcihjb3JlcykNCnJlZ2lzdGVyRG9QYXJhbGxlbChjbCkNCg0KZm9yIChpZCBpbiBzZXRkaWZmKDE6MTUsIDEzKSkgew0KICANCiAgYVNhZyA8LSBnZXQocGFzdGUwKCJzdWIiLCBpZCwgIl9hTVNMVCIpKSRhU2FnDQogIGFMYXQgPC0gZ2V0KHBhc3RlMCgic3ViIiwgaWQsICJfYU1TTFQiKSkkYUxhdA0KICB0RXhwIDwtIGdldChwYXN0ZTAoInN1YiIsIGlkLCAiX2FNU0xUIikpJHRpbWUNCiAgDQogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMgTW9kZSBEZXB0aA0KICBpbkNvbnRyb2xTYWcgPC0gYVNhZ1sxOjUwMCxdDQogIGluQ29udHJvbExhdCA8LSBhTGF0WzE6NTAwLF0NCiAgaW5Db250cm9sRGVwdGggPC0ge01GSEQoeTE9aW5Db250cm9sU2FnLCB5Mj1pbkNvbnRyb2xMYXQsIGFscGhhPTAuMTI1LCBCZXRhPTAuNSl9JE1GSERkZXB0aFsxLF0NCiAgDQogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMgZm9yZWFjaA0KICBvbmxpbmVEZXB0aCA8LSBjKCkNCiAgZW5kIDwtIG5yb3coYVNhZykNCiAgb25saW5lRGVwdGggPC0gZm9yZWFjaChpPTUwMTplbmQsIC5wYWNrYWdlcyA9IGMoJ2ZkYS51c2MnLCAndGlkeXZlcnNlJywgJ01GSEQnKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAuY29tYmluZT0nYycpICVkb3BhciUgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVtcDEgPC0gYVNhZyAlPiUgLltpLF0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlbXAyIDwtIGFMYXQgJT4lIC5baSxdDQogICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFwcGVuZDEgPC0gcmJpbmQoaW5Db250cm9sU2FnLCB0ZW1wMSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFwcGVuZDIgPC0gcmJpbmQoaW5Db250cm9sTGF0LCB0ZW1wMikNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVtcDMgPC0gTUZIRCh5MT1hcHBlbmQxLCB5Mj1hcHBlbmQyLCBhbHBoYT0wLjEyNSwgQmV0YT0wLjUpDQogICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlbXA0IDwtIHRlbXAzJE1GSERkZXB0aCAlPiUgLlsxLDUwMV0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlbXA0DQogICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICANCiAgTUZIRGRlcHRoIDwtIGMoaW5Db250cm9sRGVwdGgsIG9ubGluZURlcHRoKQ0KICANCiAgYXNzaWduKHBhc3RlMCgic3ViIiwgaWQsICJfTUZIRCIpLA0KICAgICAgICAgbGlzdChNRkhEZGVwdGg9TUZIRGRlcHRoLCB0RXhwPXRFeHApKQ0KfQ0KDQpzYXZlKHN1YjFfTUZIRCwgc3ViMl9NRkhELCBzdWIzX01GSEQsIHN1YjRfTUZIRCwgc3ViNV9NRkhELCBzdWI2X01GSEQsIHN1YjdfTUZIRCwgc3ViOF9NRkhELA0KICAgICBzdWI5X01GSEQsIHN1YjEwX01GSEQsIHN1YjExX01GSEQsIHN1YjEyX01GSEQsIHN1YjE0X01GSEQsIHN1YjE1X01GSEQsDQogICAgIGZpbGU9Ii4uL0RhdGEvckRhdGEvTUZIRC5SZGF0YSIpDQoNCmBgYA0KDQojIyBWaXN1YWxpemUgdGhlIE1GSEQgRGVwdGggey50YWJzZXQgLnRhYnNldC1mYWRlfQ0KDQpgYGB7ciB2aXNfTUZIRCwgZmlnLmFsaWduPSJjZW50ZXIiLCByZXN1bHRzPSJhc2lzIiwgb3V0LndpZHRoPSIxMDAlIn0NCg0KbG9hZChmaWxlPSIuLi9EYXRhL3JEYXRhL01GSEQuUmRhdGEiKQ0KDQpmb3IgKGlkIGluIHNldGRpZmYoMToxNSwgMTMpKSB7DQogIGRlcHRoTUZIRCA8LSBnZXQocGFzdGUwKCJzdWIiLCBpZCwgIl9NRkhEIikpJE1GSERkZXB0aA0KICB0TUZIRCA8LSBnZXQocGFzdGUwKCJzdWIiLCBpZCwgIl9NRkhEIikpJHRFeHANCiAgDQogIGNhdCgiIyMjIiwgcGFzdGUwKCJTdWJqZWN0IiwgaWQsICIgTUZIRCBEZXB0aCIpLCAiey19IiwnXG4nKQ0KICBwbG90KHRNRkhELCBkZXB0aE1GSEQsIHBjaD0xNiwgY2V4PTEsY29sPWMocmVwKCJyZWQiLCA1MDApLCByZXAoImJsYWNrIiwgbGVuZ3RoKGRlcHRoTUZIRCktNTAwKSkpDQogIGxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQ9YygiQmFzZWxpbmUgZGF0YSIsICJOZXcgZGF0YSIpLCBjb2w9YygicmVkIiwgImJsYWNrIiksDQogICAgICAgICBwY2g9MTYpDQogIGNhdCgnXG4gXG4nKQ0KICANCn0NCg0KYGBgDQoNCg0KLS0tDQoNCiMgUmVmZXJlbmNlcyB7LX0NCg==